多线程,是实现多个线程并发执行的技术,对于单核CPU来说,虽然在硬件的限制下同时只能执行一个线程,但是操作系统可以快速的在不同线程之间切换,在很小的切换时间下,来给用户造成一种同时运行多个线程的假象,而对于多核CPU,因为有硬件的支持而能够在同一时间同时运行多个线程
iOS中有以下几种线程方案

  1. Pthread
  2. NSThread
  3. GCD
  4. NSOperationQueue

Pthread

Pthread是一套通用的多线程方案,可以在类Unix操作系统(如Unix,Linux,Mac OS)等系统使用,它使用C语言编写,需要程序员自己管理线程的生命周期,使用难度较大,在开发中几乎不会直接使用它。

使用pthread创建一个线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//声明一个thread变量
pthread_t thread;

//创建并开启thread,执行test函数
pthread_create(&thread, NULL, test, NULL);

//设置该线程的状态为detach,使该线程执行结束后自动释放所有资源
pthread_detach(thread);
NSLog(@"Current Thread1:%@", [NSThread currentThread]); //打印结果:Current Thread1:<NSThread: 0x60000360acc0>{number = 1, name = main}

void *test(void *param){
NSLog(@"Current Thread12:%@", [NSThread currentThread]);//打印结果:Current Thread1:<NSThread: 0x60000361ad00>{number = 6, name = (null)}
return NULL;
}

打印结果:

1
2
2019-12-04 21:37:25.502129+0800 Thread[2601:163643] Current Thread2:<NSThread: 0x600002aa5040>{number = 6, name = (null)}
2019-12-04 21:37:25.503243+0800 Thread[2601:163558] Current Thread1:<NSThread: 0x600002ad5040>{number = 1, name = main}

可以看到在pthread中通过pthread_create函数来创建线程,pthread_create(&thread, NULL, test, NULL)有四个参数,各项参数含义如下:

第一个参数:指向线程(标识符)的指针
第二个参数:用来设置线程属性 ,通常设置为NULL
第三个参数:线程运行的函数地址
第四个参数:运行函数的参数
其中第一、二个参数比较简单,这里再额外介绍一下第三四个参数,
第三个参数”线程属性“是一个pthread_attr_t类型的结构体,这个结构体如下:

1
2
3
4
5
6
7
8
9
10
11
12
typedef struct
{
int detachstate; //线程的分离状态
int schedpolicy; //线程调度策略
struct sched_param schedparam; //线程的调度参数
int inheritsched; //线程的继承性
int scope; //线程的作用域
size_t guardsize; //线程栈末尾的警戒缓冲区大小
int stackaddr_set;
void *stackaddr; //线程栈的位置
size_t stacksize; //线程栈的大小
}pthread_attr_t;

要使用pthread_attr_t我们需要对它进行初始化,使用后还要去初始化
pthread_attr_t初始化函数:

1
pthread_attr_init()

pthread_attr_t去初始化函数:

1
pthread_attr_destory()

pthread_attr_init之后,pthread_t结构所包含的内容就是操作系统实现支持的线程所有属性的默认值

如果想修改这些默认值,pthread_attr_t的每一个属性都有相应的函数对其进行查看和修改

比如修改分离状态可以用:

1
2
int pthread_attr_getdetachstate(const pthread_attr_t * attr, int * detachstate);
int pthread_attr_setdetachstate(pthread_attr_t * attr, int detachstate);

设置的时候可以有两种选择:
<1>.detachstate参数为:PTHREAD_CREATE_DETACHED 分离状态启动
<2>.detachstate参数为:PTHREAD_CREATE_JOINABLE 正常启动线程

线程的继承性:

1
2
int pthread_attr_getinheritsched(const pthread_attr_t *attr,int *inheritsched);
int pthread_attr_setinheritsched(pthread_attr_t *attr,int inheritsched);

参数支持:
PTHREAD_INHERIT_SCHED: 新的线程继承创建线程的策略和参数
PTHREAD_EXPLICIT_SCHED:新的线程继承策略和参数来自于 schedpolicy和schedparam属性中显式 设置的调度信息

pthread_attr_t的其他属性都有相应的函数来修改,这里就不一一介绍了,感兴趣的同学可以在stackoverflow的when pthread_attr_t is not NULL?问题中了解到更多信息

第四个参数:
运行函数传递的参数,当传递单个参数时,可以直接定义一个变量传递给线程函数,当需要传递多个参数时候,就需要创建一个结构体来包含所有的参数,然后再传入线程函数:

传单个参数的情况

1
2
3
4
5
6
7
8
9
10
//定义一个函数
void *test(void *param){
NSLog(@"test:%d", *(int *)param);
return NULL;
}

//声明变量,创建线程,调用函数传递参数
int i = 100;
pthread_create(&thread, NULL, test, &i);
pthread_detach(thread);

传多个参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//定义一个结构体
struct parameters{
int size;
int count;
};

//定义函数
void *test(void *param){
struct paramaters *args;
args = (struct paramaters *)param;
NSLog(@"test:size:%d, count:%d", args->size, args->count);
return NULL;
}

//声明结构体变量,结构体赋值,创建线程,调用函数传递参数
struct paramaters args;
args.size = 100;
args.count = 300;
pthread_create(&thread, NULL, test, &args);
pthread_detach(thread);

除了pthread_create,Pthread 还有以下相关函数:

  • pthread_exit() 终止当前线程
  • pthread_cancel() 中断另外一个线程的运行
  • pthread_join() 阻塞当前的线程,直到另外一个线程运行结束
  • pthread_attr_init() 初始化线程的属性
  • pthread_attr_setdetachstate() 设置脱离状态的属性(决定这个线程在终止时是否可以被结合)
  • pthread_attr_getdetachstate() 获取脱离状态的属性
  • pthread_attr_destroy() 删除线程的属性
  • pthread_kill() 向线程发送一个信号

我们通常并不会直接使用pthread来管理线程,所以对pthread不再做过多的介绍

NSThread

NSThread和pthread都是对内核 mach kernal的mach thread的封装,不同的是NSThread是由苹果官方提供的,可以直接操作线程对象,使用起来比pthread更加易用,不过仍需要自己管理生命周期、同步、加锁的问题

创建线程:

1
2
3
4
5
6
7
//先创建线程,再启动线程
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(sailTickets) object:nil];
[thread start];
//先创建线程后自动启动线程
[NSThread detachNewThreadSelector:@selector(test) toTarget:self withObject:nil];
//隐式创建并启动线程
[self performSelectorInBackground:@selector(test) withObject:nil];

线程相关方法

1
2
3
4
5
6
7
8
9
10
11
12
// 获得主线程
+ (NSThread *)mainThread;
// 判断是否为主线程(对象方法)
- (BOOL)isMainThread;
// 判断是否为主线程(类方法)
+ (BOOL)isMainThread;
// 获得当前线程
NSThread *current = [NSThread currentThread];
// 线程的名字——setter方法
- (void)setName:(NSString *)n;
// 线程的名字——getter方法
- (NSString *)name;

线程状态控制方法

1
2
3
4
- (void)start;// 线程进入就绪状态 -> 运行状态。当线程任务执行完毕,自动进入死亡状态
+ (void)sleepUntilDate:(NSDate *)date;// 线程进入阻塞状态
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;// 线程进入阻塞状态
+ (void)exit;// 线程进入死亡状态

线程之间的通信
在开发中,我们经常会在子线程进行耗时操作,操作结束后再回到主线程去刷新 UI。这就涉及到了子线程和主线程之间的通信。我们先来了解一下官方关于 NSThread 的线程间通信的方法

1
2
3
4
5
6
7
8
9
10
11
12
// 在主线程上执行操作
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray<NSString *> *)array;

// 在指定线程上执行操作
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array NS_AVAILABLE(10_5, 2_0);
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait NS_AVAILABLE(10_5, 2_0);

// 在当前线程上执行操作,调用 NSObject 的 performSelector:相关方法
- (id)performSelector:(SEL)aSelector;
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;

线程的五个状态
New Runnabled Running Blocked Dead

New:

1
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadDemo) object:nil];

Runnabled:

1
[thread start];

Running
CPU负责调度可调度线程池中的处于就绪状态的线程,线程在执行结束之前,状态可能在就绪和运行之间来回的切换,就绪和运行之间的状态切换由CPU来完成,我们无法干涉

阻塞
正在运行的线程,当满足某个条件时,可以用休眠或者锁来阻塞线程的执行

1
2
3
4
5
6
//sleepForTimeInterval:休眠指定时长
[NSThread sleepForTimeInterval:1.0];
//sleepUntilDate:休眠到指定日期
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]];
//互斥锁
@synchronized(self)

Dead
线程正常死亡:线程执行结束
线程非正常死亡:线程突然崩溃/当满足某个条件后,在线程内部强制退出,调用exit方法

关于exit需要注意的地方:
不能在主线程中调用该方法.会使主线程退出.
当当前线程死亡之后,这个线程中的代码都不会被执行.
在调用此方法之前一定要注意释放之前由C语言框架创建的对象

线程状态的转换
当我们创建了一个线程,执行start方法后,系统把线程放入可调度线程池中,此时线程进入就绪(Runnabled)状态
如果CPU调度当前线程,则当前线程进入运行状态,当CPU调度其他线程,则当前线程回到就绪状态
如果系统在运行当前线程时候调用了sleep方法/等待同步锁,则当前线程进入阻塞状态,等到sleep到时/得到同步锁,则回到就绪状态
如果系统在运行当前线程对象的时候线程任务执行完毕/异常强制退出,则当前线程对象进入死亡状态

线程常驻
在一些场景中需要用到常驻线程,要使线程常驻就需要用到RunLoop,每个线程都有一个RunLoop这是默认没有开启,RunLoop的开启就意味着线程的常驻,比如主线程

下边这段代码来自AFNetworking

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
+ (NSThread *)networkRequestThread {
static NSThread *_networkRequestThread = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil];
[_networkRequestThread start];
});
return _networkRequestThread;
}
+ (void)networkRequestThreadEntryPoint:(id)__unused object {
@autoreleasepool {
[[NSThread currentThread] setName:@"AFNetworking"];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];
}
}

这里的_networkRequestThread就属于常驻线程,可以看到要想创建一个常驻线程,只要以下几步:
1.创建一个全局的thread变量

1
@property (nonatomic, strong) NSThread *networkThread;

2.初始化线程并启动

1
2
self.networkThread = [[NSThread alloc]initWithTarget:self selector:@selector(run) object:nil];
[self.networkThread start];

3.在线程中开启RunLoop,子线程中的RunLoop是默认关闭的

1
2
3
4
5
- (void)run{
NSLog(@"%@", [NSThread currentThread]);
[[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
}

现在networkThread已经实现了常驻,当需要使用它的时候可以这么调用
4.利用常驻线程处理任务

1
[self performSelector:@selector(action) onThread:self.networkThread withObject:nil waitUntilDone:NO ];